package org.checkerframework.framework.util;

import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.interning.qual.Interned;
import org.checkerframework.checker.nullness.qual.KeyFor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.qual.PolymorphicQualifier;
import org.checkerframework.framework.qual.SubtypeOf;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TypeSystemError;
import org.plumelib.util.StringsPlume;

/**
 * This is the default implementation of {@link QualifierKindHierarchy}.
 *
 * <p>By default, the subtyping information and information about polymorphic qualifiers is read
 * from meta-annotations on the annotation classes. This information is used to infer further
 * information such as top and bottom qualifiers. Subclasses can override the following methods to
 * change this behavior:
 *
 * <ul>
 *   <li>{@link #createQualifierKinds(Collection)}
 *   <li>{@link #createDirectSuperMap()}
 *   <li>{@link #initializePolymorphicQualifiers()}
 *   <li>{@link #initializeQualifierKindFields(Map)}
 *   <li>{@link #createLubsMap()}
 *   <li>{@link #createGlbsMap()}
 * </ul>
 *
 * {@link DefaultQualifierKindHierarchy.DefaultQualifierKind} is the implementation used for {@link
 * QualifierKind} by this class.
 */
@AnnotatedFor("nullness")
public class DefaultQualifierKindHierarchy implements QualifierKindHierarchy {

  /**
   * A mapping from canonical name of a qualifier class to the QualifierKind object representing
   * that class.
   */
  protected final Map<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind;

  /**
   * A list of all {@link QualifierKind}s for this DefaultQualifierKindHierarchy, sorted in
   * ascending order.
   */
  protected final List<DefaultQualifierKind> qualifierKinds;

  /** All the qualifier kinds that are the top qualifier in their hierarchy. */
  private final Set<DefaultQualifierKind> tops;

  /** All the qualifier kinds that are the bottom qualifier in their hierarchy. */
  private final Set<DefaultQualifierKind> bottoms;

  /**
   * Holds the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1
   * and kind2.
   */
  private final Map<QualifierKind, Map<QualifierKind, QualifierKind>> lubs;

  /**
   * Holds the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1
   * and kind2.
   */
  private final Map<QualifierKind, Map<QualifierKind, QualifierKind>> glbs;

  @Override
  public Set<? extends QualifierKind> getTops() {
    return tops;
  }

  @Override
  public Set<? extends QualifierKind> getBottoms() {
    return bottoms;
  }

  @Override
  public @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2) {
    @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in lubs.
    QualifierKind result = lubs.get(q1).get(q2);
    return result;
  }

  @Override
  public @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2) {
    @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in glbs.
    QualifierKind result = glbs.get(q1).get(q2);
    return result;
  }

  @Override
  public List<? extends QualifierKind> allQualifierKinds() {
    return qualifierKinds;
  }

  @Override
  public QualifierKind getQualifierKind(
      @UnknownInitialization(DefaultQualifierKindHierarchy.class) DefaultQualifierKindHierarchy this,
      @CanonicalName String name) {
    QualifierKind result = nameToQualifierKind.get(name);
    if (result == null) {
      throw new BugInCF("getQualifierKind(%s) => null", name);
    }
    return result;
  }

  /**
   * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its
   * qualifier kinds.
   *
   * @param qualifierClasses all the classes of qualifiers supported by this hierarchy
   */
  @SuppressWarnings("this-escape")
  public DefaultQualifierKindHierarchy(Collection<Class<? extends Annotation>> qualifierClasses) {
    this(qualifierClasses, null, null);
  }

  /**
   * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its
   * qualifier kinds.
   *
   * <p>For some type systems, qualifiers may be added at run time, so the {@link SubtypeOf}
   * meta-annotation on the bottom qualifier class cannot specify all other qualifiers. For those
   * type systems, use this constructor. Otherwise, use {@link
   * #DefaultQualifierKindHierarchy(Collection)}.
   *
   * @param qualifierClasses all the classes of qualifiers supported by this hierarchy
   * @param bottom the bottom qualifier of this hierarchy
   */
  @SuppressWarnings("this-escape")
  public DefaultQualifierKindHierarchy(
      Collection<Class<? extends Annotation>> qualifierClasses,
      Class<? extends Annotation> bottom) {
    this(qualifierClasses, bottom, null);
  }

  /**
   * Private constructor that sets the bottom qualifier if {@code bottom} is nonnull.
   *
   * @param qualifierClasses all the classes of qualifiers supported by this hierarchy
   * @param bottom the bottom qualifier of this hierarchy or null if bottom can be inferred from the
   *     meta-annotations
   * @param voidParam void parameter to differentiate from {@link
   *     #DefaultQualifierKindHierarchy(Collection, Class)}
   */
  @SuppressWarnings("this-escape")
  private DefaultQualifierKindHierarchy(
      Collection<Class<? extends Annotation>> qualifierClasses,
      @Nullable Class<? extends Annotation> bottom,
      @SuppressWarnings("UnusedVariable") Void voidParam) {
    this.nameToQualifierKind = createQualifierKinds(qualifierClasses);
    this.qualifierKinds = new ArrayList<>(nameToQualifierKind.values());
    Collections.sort(qualifierKinds);

    Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap = createDirectSuperMap();
    if (bottom != null) {
      setBottom(bottom, directSuperMap);
    }
    this.tops = createTopsSet(directSuperMap);
    this.bottoms = createBottomsSet(directSuperMap);
    initializePolymorphicQualifiers();
    initializeQualifierKindFields(directSuperMap);
    this.lubs = createLubsMap();
    this.glbs = createGlbsMap();

    verifyHierarchy(directSuperMap);
  }

  /**
   * Verifies that the {@link DefaultQualifierKindHierarchy} is a valid hierarchy.
   *
   * @param directSuperMap mapping from qualifier to its direct supertypes; used to verify that a
   *     polymorphic annotation does not have a {@link SubtypeOf} meta-annotation
   * @throws TypeSystemError if the hierarchy isn't valid
   */
  @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"})
  protected void verifyHierarchy(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
      boolean isPoly = qualifierKind.isPoly();
      boolean hasSubtypeOfAnno = directSuperMap.containsKey(qualifierKind);
      if (isPoly && hasSubtypeOfAnno) {
        // Polymorphic qualifiers with upper and lower bounds are currently not supported.
        throw new TypeSystemError(
            "AnnotatedTypeFactory: "
                + qualifierKind
                + " is polymorphic and specifies super qualifiers.%n"
                + "Remove the @PolymorphicQualifier or @SubtypeOf annotation from it.");
      } else if (!isPoly && !hasSubtypeOfAnno) {
        throw new TypeSystemError(
            "AnnotatedTypeFactory: %s does not specify its super qualifiers.%n"
                + "Add an @SubtypeOf or @PolymorphicQualifier annotation to it,%n"
                + "or if it is an alias, exclude it from `createSupportedTypeQualifiers()`.",
            qualifierKind);
      } else if (isPoly) {
        if (qualifierKind.top == null) {
          throw new TypeSystemError(
              "PolymorphicQualifier, %s, has to specify a type hierarchy in its"
                  + " @PolymorphicQualifier meta-annotation, if more than one exists;"
                  + " top types: [%s].",
              qualifierKind, StringsPlume.join(", ", tops));
        } else if (!tops.contains(qualifierKind.top)) {
          throw new TypeSystemError(
              "Polymorphic qualifier %s has invalid top %s. Top qualifiers: %s",
              qualifierKind, qualifierKind.top, StringsPlume.join(", ", tops));
        }
      }
    }

    if (bottoms.size() != tops.size()) {
      throw new TypeSystemError(
          "Number of tops not equal to number of bottoms: Tops: [%s] Bottoms: [%s]",
          StringsPlume.join(", ", tops), StringsPlume.join(", ", bottoms));
    }
  }

  /**
   * Creates all QualifierKind objects for the given qualifier classes and adds them to
   * qualifierClassMap. This method does not initialize all fields in the {@link QualifierKind};
   * that is done by {@link #initializeQualifierKindFields(Map)}.
   *
   * @param qualifierClasses classes of annotations that are type qualifiers
   * @return a mapping from the canonical name of an annotation class to {@link QualifierKind}
   */
  protected Map<@Interned @CanonicalName String, DefaultQualifierKind> createQualifierKinds(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      Collection<Class<? extends Annotation>> qualifierClasses) {
    TreeMap<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind =
        new TreeMap<>();
    for (Class<? extends Annotation> clazz : qualifierClasses) {
      @SuppressWarnings("interning") // uniqueness is tested immediately below
      @Interned DefaultQualifierKind qualifierKind = new DefaultQualifierKind(clazz);
      if (nameToQualifierKind.containsKey(qualifierKind.getName())) {
        throw new TypeSystemError("Duplicate QualifierKind " + qualifierKind.getName());
      }
      nameToQualifierKind.put(qualifierKind.getName(), qualifierKind);
    }
    return Collections.unmodifiableMap(nameToQualifierKind);
  }

  /**
   * Creates a mapping from a {@link QualifierKind} to a set of its direct super qualifier kinds.
   * The direct super qualifier kinds do not contain the qualifier itself. This mapping is used to
   * create the bottom set, to create the top set, and by {@link
   * #initializeQualifierKindFields(Map)}.
   *
   * <p>This implementation uses the {@link SubtypeOf} meta-annotation. Subclasses may override this
   * method to create the direct super map some other way.
   *
   * <p>Note that this method is called from the constructor when {@link #nameToQualifierKind} and
   * {@link #qualifierKinds} are the only fields that have non-null values. This method is not
   * static, so it can be overridden by subclasses.
   *
   * @return a mapping from each {@link QualifierKind} to a set of its direct super qualifiers
   */
  @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"})
  protected Map<DefaultQualifierKind, Set<DefaultQualifierKind>> createDirectSuperMap(
      @UnderInitialization DefaultQualifierKindHierarchy this) {
    Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap = new TreeMap<>();
    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
      SubtypeOf subtypeOfMetaAnno =
          qualifierKind.getAnnotationClass().getAnnotation(SubtypeOf.class);
      if (subtypeOfMetaAnno == null) {
        // qualifierKind has no @SubtypeOf: it must be top or polymorphic
        continue;
      }
      Set<DefaultQualifierKind> directSupers = new TreeSet<>();
      for (Class<? extends Annotation> superClazz : subtypeOfMetaAnno.value()) {
        String superName = QualifierKindHierarchy.annotationClassName(superClazz);
        DefaultQualifierKind superQualifier = nameToQualifierKind.get(superName);
        if (superQualifier == null) {
          throw new TypeSystemError(
              "In %s, @SubtypeOf(%s) argument isn't in the hierarchy."
                  + " Have you mis-defined createSupportedTypeQualifiers()? Qualifiers: [%s]",
              qualifierKind, superName, StringsPlume.join(", ", qualifierKinds));
        }
        directSupers.add(superQualifier);
      }
      directSuperMap.put(qualifierKind, directSupers);
    }
    return directSuperMap;
  }

  /**
   * This method sets bottom to the given class and modifies {@code directSuperMap} to add all
   * leaves to its super qualifier kinds. Leaves are qualifier kinds that are not super qualifier
   * kinds of another qualifier kind and are not polymorphic.
   *
   * @param bottom the class of the bottom qualifier in the hierarchy
   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
   *     qualifiers; side-effected by this method
   */
  @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"})
  private void setBottom(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      Class<? extends Annotation> bottom,
      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
    DefaultQualifierKind bottomKind =
        nameToQualifierKind.get(QualifierKindHierarchy.annotationClassName(bottom));
    if (bottomKind == null) {
      throw new TypeSystemError(
          "QualifierKindHierarchy#setBottom: %s is not in the hierarchy",
          bottom.getCanonicalName());
    }

    Set<DefaultQualifierKind> leaves = new TreeSet<>(qualifierKinds);
    leaves.remove(bottomKind);
    directSuperMap.forEach((sub, supers) -> leaves.removeAll(supers));
    Set<DefaultQualifierKind> bottomDirectSuperQuals = directSuperMap.get(bottomKind);
    if (bottomDirectSuperQuals == null) {
      directSuperMap.put(bottomKind, leaves);
    } else {
      bottomDirectSuperQuals.addAll(leaves);
    }
  }

  /**
   * Creates the set of top {@link QualifierKind}s by searching {@code directSuperMap} for qualifier
   * kinds without any direct super qualifier kinds.
   *
   * <p>Subclasses should override {@link #createDirectSuperMap} to change the tops and not this
   * method, because other methods expect the directSuperMap to be complete.
   *
   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
   *     qualifier kinds; created by {@link #createDirectSuperMap()}
   * @return the set of top {@link QualifierKind}s
   */
  private Set<DefaultQualifierKind> createTopsSet(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
    Set<DefaultQualifierKind> tops = new TreeSet<>();
    directSuperMap.forEach(
        (qualifierKind, superQuals) -> {
          if (superQuals.isEmpty()) {
            tops.add(qualifierKind);
          }
        });
    return tops;
  }

  /**
   * Creates the set of bottom {@link QualifierKind}s by searching {@code directSuperMap} for
   * qualifiers that are not a direct super qualifier kind of another qualifier kind.
   *
   * <p>Subclasses should override {@link #createDirectSuperMap} or {@link #setBottom} to change the
   * bottoms and not this method, because other methods expect the directSuperMap to be complete.
   *
   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
   *     qualifier kinds; created by {@link #createDirectSuperMap()}
   * @return the set of bottom {@link QualifierKind}s
   */
  private Set<DefaultQualifierKind> createBottomsSet(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
    Set<DefaultQualifierKind> bottoms = new HashSet<>(directSuperMap.keySet());
    for (Set<DefaultQualifierKind> superKinds : directSuperMap.values()) {
      bottoms.removeAll(superKinds);
    }
    return bottoms;
  }

  /**
   * Iterates over all the qualifier kinds and adds all polymorphic qualifier kinds to
   * polymorphicQualifiers. Also sets {@link DefaultQualifierKind#poly} and {@link
   * DefaultQualifierKind#top} for the polymorphic qualifiers, and sets {@link
   * DefaultQualifierKind#poly} for the top qualifiers.
   *
   * <p>Requires that tops has been initialized.
   */
  @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds", "this.tops"})
  protected void initializePolymorphicQualifiers(
      @UnderInitialization DefaultQualifierKindHierarchy this) {
    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
      Class<? extends Annotation> clazz = qualifierKind.getAnnotationClass();
      PolymorphicQualifier polyMetaAnno = clazz.getAnnotation(PolymorphicQualifier.class);
      if (polyMetaAnno == null) {
        continue;
      }
      qualifierKind.poly = qualifierKind;
      String topName = QualifierKindHierarchy.annotationClassName(polyMetaAnno.value());
      if (nameToQualifierKind.containsKey(topName)) {
        qualifierKind.top = nameToQualifierKind.get(topName);
      } else if (topName.equals(Annotation.class.getCanonicalName())) {
        // Annotation.class is the default value of PolymorphicQualifier. If it is used,
        // then there must be exactly one top.
        if (tops.size() == 1) {
          qualifierKind.top = tops.iterator().next();
        } else {
          throw new TypeSystemError(
              "Polymorphic qualifier %s did not specify a top annotation class. Tops: [%s]",
              qualifierKind, StringsPlume.join(", ", tops));
        }
      } else {
        throw new TypeSystemError(
            "Polymorphic qualifier %s's top, %s, is not a qualifier.", qualifierKind, topName);
      }
      qualifierKind.strictSuperTypes = Collections.singleton(qualifierKind.top);
      qualifierKind.top.poly = qualifierKind;
    }
  }

  /**
   * For each qualifier kind in {@code directSuperMap}, initializes {@link
   * DefaultQualifierKind#strictSuperTypes}, {@link DefaultQualifierKind#top}, {@link
   * DefaultQualifierKind#bottom}, and {@link DefaultQualifierKind#poly}.
   *
   * <p>Requires tops, bottoms, and polymorphicQualifiers to be initialized.
   *
   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
   *     qualifier kinds; created by {@link #createDirectSuperMap()}
   */
  @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"})
  protected void initializeQualifierKindFields(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
    for (DefaultQualifierKind qualifierKind : directSuperMap.keySet()) {
      if (!qualifierKind.isPoly()) {
        qualifierKind.strictSuperTypes = findAllTheSupers(qualifierKind, directSuperMap);
      }
    }
    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
      for (DefaultQualifierKind top : tops) {
        if (qualifierKind.isSubtypeOf(top)) {
          if (qualifierKind.top == null) {
            qualifierKind.top = top;
          } else if (qualifierKind.top != top) {
            throw new TypeSystemError(
                "Multiple tops found for qualifier %s. Tops: %s and %s.",
                qualifierKind, top, qualifierKind.top);
          }
        }
      }
      if (qualifierKind.top == null) {
        throw new TypeSystemError(
            "Qualifier %s isn't a subtype of any top. tops = %s", qualifierKind, tops);
      }
      qualifierKind.poly = qualifierKind.top.poly;
    }
    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
      for (DefaultQualifierKind bot : bottoms) {
        if (bot.top != qualifierKind.top) {
          continue;
        }
        if (qualifierKind.bottom == null) {
          qualifierKind.bottom = bot;
        } else if (qualifierKind.top != bot) {
          throw new TypeSystemError(
              "Multiple bottoms found for qualifier %s. Bottoms: %s and %s.",
              qualifierKind, bot, qualifierKind.bottom);
        }
        if (qualifierKind.isPoly()) {
          assert bot.strictSuperTypes != null
              : "@AssumeAssertion(nullness): strictSuperTypes should be nonnull.";
          bot.strictSuperTypes.add(qualifierKind);
        }
      }
      if (qualifierKind.bottom == null) {
        throw new TypeSystemError(
            "Cannot find a bottom qualifier for %s. bottoms = %s", qualifierKind, bottoms);
      }
    }
  }

  /**
   * Returns the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}.
   *
   * @param qualifierKind the qualifier kind whose super types should be returned
   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
   *     qualifier kinds; created by {@link #createDirectSuperMap()}
   * @return the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}
   */
  private Set<QualifierKind> findAllTheSupers(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      @KeyFor("#2") QualifierKind qualifierKind,
      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {

    Set<QualifierKind> allSupers = new TreeSet<>(directSuperMap.get(qualifierKind));

    // Visit every super qualifier kind and add its super qualifier kinds to allSupers.
    Queue<DefaultQualifierKind> toVisit = new ArrayDeque<>(directSuperMap.get(qualifierKind));
    Set<DefaultQualifierKind> visited = new HashSet<>();
    while (!toVisit.isEmpty()) {
      DefaultQualifierKind superQualKind = toVisit.remove();
      if (superQualKind == qualifierKind) {
        throw new TypeSystemError("Cycle in hierarchy: %s", qualifierKind);
      }

      if (!visited.add(superQualKind) || superQualKind.isPoly()) {
        continue;
      }

      Set<DefaultQualifierKind> superSuperQuals = directSuperMap.get(superQualKind);
      if (superSuperQuals == null) {
        throw new TypeSystemError(
            superQualKind
                + " is not a key in the directSuperMap."
                + "  Does it have a @SubtypeOf annotation?");
      }
      toVisit.addAll(superSuperQuals);
      allSupers.addAll(superSuperQuals);
    }
    return allSupers;
  }

  /**
   * Creates the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1
   * and kind2.
   *
   * @return a mapping of lubs
   */
  @RequiresNonNull("this.qualifierKinds")
  protected Map<QualifierKind, Map<QualifierKind, QualifierKind>> createLubsMap(
      @UnderInitialization DefaultQualifierKindHierarchy this) {
    Map<QualifierKind, Map<QualifierKind, QualifierKind>> lubs = new HashMap<>();
    for (QualifierKind qual1 : qualifierKinds) {
      for (QualifierKind qual2 : qualifierKinds) {
        if (qual1.getTop() != qual2.getTop()) {
          continue;
        }
        QualifierKind lub = findLub(qual1, qual2);
        addToMapOfMap(lubs, qual1, qual2, lub, "lub");
        addToMapOfMap(lubs, qual2, qual1, lub, "lub");
      }
    }
    return lubs;
  }

  /**
   * Returns the least upper bound of {@code qual1} and {@code qual2}.
   *
   * @param qual1 a qualifier kind
   * @param qual2 a qualifier kind
   * @return the least upper bound of {@code qual1} and {@code qual2}
   */
  private QualifierKind findLub(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      QualifierKind qual1,
      QualifierKind qual2) {
    if (qual1 == qual2) {
      return qual1;
    } else if (qual1.isSubtypeOf(qual2)) {
      return qual2;
    } else if (qual2.isSubtypeOf(qual1)) {
      return qual1;
    }
    Set<QualifierKind> allSuperTypes = new TreeSet<>(qual1.getStrictSuperTypes());
    Set<? extends QualifierKind> qual2StrictSuperTypes = qual2.getStrictSuperTypes();
    allSuperTypes.retainAll(qual2StrictSuperTypes);
    Set<? extends QualifierKind> lubs = findLowestQualifiers(allSuperTypes);
    if (lubs.size() != 1) {
      throw new TypeSystemError(
          "lub(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", lubs));
    }
    QualifierKind lub = lubs.iterator().next();
    if (lub.isPoly() && !qual1.isPoly() && !qual2.isPoly()) {
      throw new TypeSystemError("lub(%s, %s) can't be poly: %s", qual1, qual2, lub);
    }
    return lub;
  }

  /**
   * Returns the lowest qualifiers in the passed set.
   *
   * @param qualifierKinds a set of qualifiers
   * @return the lowest qualifiers in the passed set
   */
  protected static Set<QualifierKind> findLowestQualifiers(Set<QualifierKind> qualifierKinds) {
    Set<QualifierKind> lowestQualifiers = new TreeSet<>(qualifierKinds);
    for (QualifierKind a1 : qualifierKinds) {
      lowestQualifiers.removeIf(a2 -> a1 != a2 && a1.isSubtypeOf(a2));
    }
    return lowestQualifiers;
  }

  /**
   * Creates the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1
   * and kind2.
   *
   * @return a mapping of glb
   */
  @RequiresNonNull("this.qualifierKinds")
  protected Map<QualifierKind, Map<QualifierKind, QualifierKind>> createGlbsMap(
      @UnderInitialization DefaultQualifierKindHierarchy this) {
    Map<QualifierKind, Map<QualifierKind, QualifierKind>> glbs = new TreeMap<>();
    for (QualifierKind qual1 : qualifierKinds) {
      for (QualifierKind qual2 : qualifierKinds) {
        if (qual1.getTop() != qual2.getTop()) {
          continue;
        }
        QualifierKind glb = findGlb(qual1, qual2);
        addToMapOfMap(glbs, qual1, qual2, glb, "glb");
        addToMapOfMap(glbs, qual2, qual1, glb, "glb");
      }
    }
    return glbs;
  }

  /**
   * Returns the greatest lower bound of {@code qual1} and {@code qual2}.
   *
   * @param qual1 a qualifier kind
   * @param qual2 a qualifier kind
   * @return the greatest lower bound of {@code qual1} and {@code qual2}
   */
  @RequiresNonNull("this.qualifierKinds")
  private QualifierKind findGlb(
      @UnderInitialization DefaultQualifierKindHierarchy this,
      QualifierKind qual1,
      QualifierKind qual2) {
    if (qual1 == qual2) {
      return qual1;
    } else if (qual1.isSubtypeOf(qual2)) {
      return qual1;
    } else if (qual2.isSubtypeOf(qual1)) {
      return qual2;
    }
    Set<QualifierKind> allSubTypes = new TreeSet<>();
    for (QualifierKind qualifierKind : qualifierKinds) {
      if (qualifierKind.isSubtypeOf(qual1) && qualifierKind.isSubtypeOf(qual2)) {
        allSubTypes.add(qualifierKind);
      }
    }
    Set<QualifierKind> glbs = findHighestQualifiers(allSubTypes);
    if (glbs.size() != 1) {
      throw new TypeSystemError(
          "glb(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", glbs));
    }
    QualifierKind glb = glbs.iterator().next();
    if (glb.isPoly() && !qual1.isPoly() && !qual2.isPoly()) {
      throw new TypeSystemError("glb(%s, %s) can't be poly: %s", qual1, qual2, glb);
    }
    return glb;
  }

  /**
   * Returns the highest qualifiers in the passed set.
   *
   * @param qualifierKinds a set of qualifiers
   * @return the highest qualifiers in the passed set
   */
  protected static Set<QualifierKind> findHighestQualifiers(Set<QualifierKind> qualifierKinds) {
    Set<QualifierKind> highestQualifiers = new TreeSet<>(qualifierKinds);
    for (QualifierKind a1 : qualifierKinds) {
      highestQualifiers.removeIf(a2 -> a1 != a2 && a2.isSubtypeOf(a1));
    }
    return highestQualifiers;
  }

  /**
   * Add Key: qual1, Value: (Key: qual2, Value: value) to {@code map}. If already in map, throw an
   * exception if value is different.
   *
   * @param map mapping to side-effect
   * @param qual1 the first qualifier kind
   * @param qual2 the second qualifier kind
   * @param value the value to add
   * @param operationName "lub" or "glb"; used only for error messages
   */
  private static void addToMapOfMap(
      Map<QualifierKind, Map<QualifierKind, QualifierKind>> map,
      QualifierKind qual1,
      QualifierKind qual2,
      QualifierKind value,
      String operationName) {
    Map<QualifierKind, QualifierKind> qual1Map = map.computeIfAbsent(qual1, k -> new HashMap<>());
    QualifierKind existingValue = qual1Map.get(qual2);
    if (existingValue == null) {
      qual1Map.put(qual2, value);
    } else {
      if (existingValue != value) {
        throw new TypeSystemError(
            "Multiple %ss for qualifiers %s and %s. Found map %s and %s",
            operationName, qual1, qual2, value, existingValue);
      }
    }
  }

  /**
   * The default implementation of {@link QualifierKind}.
   *
   * <p>The fields in this class that refer to {@link QualifierKind}s are set when creating the
   * {@link DefaultQualifierKindHierarchy}. So the getter methods for these fields should not be
   * called until after the {@code DefaultQualifierKindHierarchy} is created.
   */
  @AnnotatedFor("nullness")
  public @Interned static class DefaultQualifierKind implements QualifierKind {

    /** The canonical name of the annotation class of this. */
    private final @Interned @CanonicalName String name;

    /** The annotation class for this. */
    private final Class<? extends Annotation> clazz;

    /** True if the annotation class of this has annotation elements/arguments. */
    private final boolean hasElements;

    /** The top of the hierarchy to which this belongs. */
    // Set while creating the QualifierKindHierarchy.
    protected @MonotonicNonNull DefaultQualifierKind top;

    /** The bottom of the hierarchy to which this belongs. */
    // Set while creating the QualifierKindHierarchy.
    protected @MonotonicNonNull DefaultQualifierKind bottom;

    /** The polymorphic qualifier of the hierarchy to which this belongs. */
    // Set while creating the QualifierKindHierarchy.
    protected @Nullable DefaultQualifierKind poly;

    /**
     * All the qualifier kinds that are a strict super qualifier kind of this. Does not include this
     * qualifier kind itself.
     */
    // Set while creating the QualifierKindHierarchy.
    protected @MonotonicNonNull Set<QualifierKind> strictSuperTypes;

    /**
     * Creates a {@link DefaultQualifierKind} for the given annotation class.
     *
     * @param clazz annotation class for a qualifier
     */
    DefaultQualifierKind(Class<? extends Annotation> clazz) {
      this.clazz = clazz;
      this.hasElements = clazz.getDeclaredMethods().length != 0;
      this.name = QualifierKindHierarchy.annotationClassName(clazz).intern();
      this.poly = null;
    }

    @Override
    public @Interned @CanonicalName String getName() {
      return name;
    }

    @Override
    public Class<? extends Annotation> getAnnotationClass() {
      return clazz;
    }

    @Override
    public QualifierKind getTop() {
      if (top == null) {
        throw new BugInCF(
            "DefaultQualifierKindHierarchy#getTop: Top is null for QualifierKind %s."
                + " Don't call this method during initialization of DefaultQualifierKindHierarchy.",
            name);
      }
      return top;
    }

    @Override
    public boolean isTop() {
      return this.top == this;
    }

    @Override
    public QualifierKind getBottom() {
      if (bottom == null) {
        throw new BugInCF(
            "DefaultQualifierKind#getBottom:Bottom is null for QualifierKind %s. Don't call this"
                + " method during initialization of DefaultQualifierKindHierarchy.",
            name);
      }
      return bottom;
    }

    @Override
    public boolean isBottom() {
      return this.bottom == this;
    }

    @Override
    public @Nullable QualifierKind getPolymorphic() {
      return poly;
    }

    @Pure
    @Override
    public boolean isPoly() {
      return this.poly == this;
    }

    @Override
    public boolean hasElements() {
      return hasElements;
    }

    @Override
    public Set<? extends QualifierKind> getStrictSuperTypes() {
      if (strictSuperTypes == null) {
        throw new BugInCF(
            "DefaultQualifierKind#getStrictSuperTypes: strictSuperTypes was null. Don't call this"
                + " method during initialization of DefaultQualifierKindHierarchy.");
      }
      return strictSuperTypes;
    }

    @Override
    public boolean isInSameHierarchyAs(QualifierKind other) {
      return this.top == other.getTop();
    }

    @Override
    public boolean isSubtypeOf(QualifierKind superQualKind) {
      if (strictSuperTypes == null) {
        throw new BugInCF(
            "DefaultQualifierKind#isSubtypeOf: strictSuperTypes was null. Don't call this method"
                + " during initialization of DefaultQualifierKindHierarchy.");
      }
      return this == superQualKind || strictSuperTypes.contains(superQualKind);
    }

    @Override
    public String toString() {
      return clazz.getSimpleName();
    }
  }
}
